{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Flask 中的Cookies\r\n",
    "\r\n",
    "到目前为止，我们构建的页面都非常简单。浏览器向服务器发送请求，服务器用 HTML 页面进行响应，这就是所有的一切。是一个无状态协议。这意味着 HTTP 没有内置的方式告诉服务器，这两个请求来自同一个用户。因此，服务器不知道你是第一次还是第一千次请求这个页面。它为每个人都提供了平等的服务，就好像他们是第一次请求页面一样。\r\n",
    "\r\n",
    "访问电子商务网站并浏览一些项目。在您下次访问该网站时，您将得到一些基于您以前的浏览模式的产品推荐。那么为什么网站知道你的存在呢？\r\n",
    "\r\n",
    "答案就在 Cookies 和 Sessions 中。\r\n",
    "\r\n",
    "本课讨论 Cookies ，将在下一课讨论 Sessions。\r\n",
    "\r\n",
    "## 什么是 Cookies ? 小饼干？\r\n",
    "\r\n",
    "Cookie 只是服务器在浏览器中设置的一段数据，它是这样工作的:\r\n",
    "\r\n",
    "1. 浏览器向服务器发送网页请求\r\n",
    "2. 服务器回应浏览器的要求时,会在发送所要求的网页时,连带一个或多个 cookies 一并发送，\r\n",
    "3. 浏览器在收到响应后，会呈现网页，并将 cookie 保存在用户电脑中\r\n",
    "4. 对服务器的后续请求将包括来自Cookie标头中所有Cookie的数据。此过程将继续到 cookie 过期为止。一旦 cookie 过期，它将从浏览器中删除\r\n",
    "\r\n",
    "## 设置一个Cookies\r\n",
    "\r\n",
    "在 Flask 中，我们使用响应对象的 set_cookie()方法来设置 cookie。语法如下:\r\n",
    "\r\n",
    "`set_cookie(key, value=\"\", max_age=None)`\r\n",
    "\r\n",
    "`key` 是一个必需的参数，引用 cookie 的名称。 `value`是要存储在 cookie 中的数据，默认为空字符串。 `max_age` 指的是 cookie 的过期时间，以秒为单位，如果没有设置，则当用户关闭浏览器时 cookie 将不存在。\r\n",
    "\r\n",
    "打开 main2.py，在 contact() view 函数之后添加以下代码:\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask, render_template, request, redirect, url_for, flash, make_response\r\n",
    "#...\r\n",
    "@app.route('/cookie/')\r\n",
    "def cookie():\r\n",
    "    res = make_response(\"Setting a cookie\")\r\n",
    "    res.set_cookie('foo', 'bar', max_age=60*60*24*365*2)\r\n",
    "    return res\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "在这里，我们创建了一个名为 foo 的 cookie，它的值栏可以持续2年( 秒数=60秒*60分*24小时*365天*2年)。\r\n",
    "\r\n",
    "启动服务器`python main2.py runserver`并访问 http://localhost:5000/cookie/。\r\n",
    "\r\n",
    "您应该看到一个响应为 \"Setting a cookie\" 的页面。要查看服务器设置的 cookie，请点击F12 打开 Chrome类浏览器应用程序, 存储 (Shift + F9打开 Firefox 中的 Storage Inspector)。一个新窗口将出现在浏览器窗口的底部。在左边，选择“ Cookies”存储类型，然后点击 http://localhost:5000/ 查看服务器设置在 在 http://localhost:5000/ 的所有 Cookies 。\r\n",
    "\r\n",
    "![img](set-cookies.png)\r\n",
    "\r\n",
    "从现在开始，cookie `foo` 将与任何请求一起发送到 http://localhost:5000/ 服务器。我们可以使用 Chrome/Firefox 中的网络监视器来验证这一点。按 Ctrl + Shift + e 打开网络监视器并访问 http://localhost:5000/。在左边的网络请求列表中选择第一个请求，您将在右侧面板中获得请求详细信息，如下所示:\r\n",
    "\r\n",
    "![img](cookie_being_send_to_server.png)\r\n",
    "\r\n",
    "请注意，一旦 cookie 被设置，后续的对 http://localhost:5000/cookie 请求还将更新 cookie 的过期时间 。\r\n",
    "\r\n",
    "\r\n",
    "## 访问 Cookies\r\n",
    "\r\n",
    "要访问 cookie，我们使用 `request`  请求对象的 `cookie` 属性。 `cookie` 属性是一个类似字典的属性，它包含浏览器发送的所有 cookie。打开 main2.py 并修改 `cookie()` 视图函数，如下所示:\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```python\r\n",
    "#...\r\n",
    "@app.route('/cookie/')\r\n",
    "def cookie():\r\n",
    "    if not request.cookies.get('foo'):\r\n",
    "        res = make_response(\"Setting a cookie\")\r\n",
    "        res.set_cookie('foo', 'bar', max_age=60*60*24*365*2)\r\n",
    "    else:\r\n",
    "        res = make_response(\"Value of cookie foo is {}\".format(request.cookies.get('foo')))\r\n",
    "    return res\r\n",
    "#...\r\n",
    "```\r\n",
    "我们已经修改了视图函数，假设存在 cookie, 它会显示cookie 值。否则，它会为我们设置一个cookie。\r\n",
    "\r\n",
    "访问 http://localhost:5000/cookie/ ，这次你会得到如下回复。\r\n",
    "\r\n",
    "![img](reading_cookie_named_foo-00e14e9a-1438-4b3e-abeb-e45126c63e38.png)\r\n",
    "\r\n",
    "请求对象也可以在模板中使用。这意味着，在模板内部，我们可以使用与 Python 代码相同的方式访问 cookie。我们将在下一节中看到这方面的一个例子。\r\n",
    "\r\n",
    "## 删除 Cookies\r\n",
    "\r\n",
    "要删除具有 cookie 名称和任何值的 cookie 调用 set_cookie() 方法，并将 max_age 参数设置为0。打开 main2.py 文件，并在 cookie ()视图函数之后添加以下代码。\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```python\r\n",
    "#...\r\n",
    "@app.route('/delete-cookie/')\r\n",
    "def delete_cookie():\r\n",
    "    res = make_response(\"Cookie Removed\")\r\n",
    "    res.set_cookie('foo', 'bar', max_age=0) # 将 max_age 参数设置为0\r\n",
    "    return res\r\n",
    "#...\r\n",
    "```\r\n",
    "访问 http://127.0.0.1:5000/delete-cookie/ ，你会得到如下回复:\r\n",
    "\r\n",
    "![img](cookie_removed-3a37de58-32b9-4ce8-8f7d-1676c6536f1c.png)\r\n",
    "\r\n",
    "\r\n",
    "现在你应该对 `cookie` 的工作原理有了很好的理解。下面的清单为您提供了一个实用示例，说明如何使用 cookie 来存储用户首选项。\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "```python\r\n",
    "#...\r\n",
    "@app.route('/article/', methods=['POST', 'GET'])\r\n",
    "def article():\r\n",
    "    if request.method == 'POST':\r\n",
    "        print(request.form)\r\n",
    "        res = make_response(\"\")\r\n",
    "        res.set_cookie(\"font\", request.form.get('font'), 60*60*24*15)\r\n",
    "        res.headers['location'] = url_for('article')\r\n",
    "        return res, 302\r\n",
    "    \r\n",
    "    return render_template('article.html')\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "使用以下代码创建一个新的模板文章:\r\n",
    "\r\n",
    "```html\r\n",
    "<!DOCTYPE html>\r\n",
    "<html lang=\"en\">\r\n",
    "<head>\r\n",
    "    <meta charset=\"UTF-8\">\r\n",
    "    <title>Article</title>\r\n",
    "</head>\r\n",
    "<body style=\"{% if request.cookies.get('font') %}font-family:{{ request.cookies.get('font') }}{% endif %}\">\r\n",
    "\r\n",
    "Select Font Preference: <br>\r\n",
    "<form action=\"\" method=\"post\">\r\n",
    "    <select name=\"font\" onchange=\"submit()\">\r\n",
    "        <option value=\"\">----</option>\r\n",
    "        <option value=\"consolas\" {% if request.cookies.get('font') == 'consolas' %}selected{% endif %}>consolas</option>\r\n",
    "        <option value=\"arial\" {% if request.cookies.get('font') == 'arial' %}selected{% endif %}>arial</option>\r\n",
    "        <option value=\"verdana\" {% if request.cookies.get('font') == 'verdana' %}selected{% endif %}>verdana</option>\r\n",
    "    </select>\r\n",
    "</form>\r\n",
    "\r\n",
    "<h1>Festus, superbus toruss diligenter tractare de brevis, dexter olla.</h1>\r\n",
    "\r\n",
    "<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam blanditiis debitis doloribus eos magni minus odit, provident tempora. Expedita fugiat harum in incidunt minus nam nesciunt voluptate. Facilis nesciunt, similique!\r\n",
    "</p>\r\n",
    "\r\n",
    "<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias amet animi aperiam inventore molestiae quos, reiciendis voluptatem. Ab, cum cupiditate fugit illo incidunt ipsa neque quam, qui quidem vel voluptatum.</p>\r\n",
    "\r\n",
    "</body>\r\n",
    "</html>\r\n",
    "```\r\n",
    "\r\n",
    "当用户第一次访问 `http://localhost:5000/article` 时，页面会使用默认的浏览器字体显示。当用户使用下拉菜单更改字体时，我们提交表单。如果条件 `request.method == 'POST'` 变为 true，我们设置一个名为 `font` 的 cookie，其值为所选字体的值，该字体将在15天内到期，然后用户被重定向到 `http://localhost:5000/article`。并以选定的字体显示页面。\r\n",
    "\r\n",
    "在浏览器访问时， http://127.0.0.1:5000/article 和你将显示一个浏览器默认字体的页面。\r\n",
    "\r\n",
    "![img](article_page_in_default_browser_font-8e88e117-f123-4f70-9edd-eb626c6ea85c.png)\r\n",
    "\r\n",
    "从下拉菜单中选择字体，页面将以选定的字体显示。\r\n",
    "\r\n",
    "\r\n",
    "![img](article_page_in_consolas_font-12982245-e1bd-44ad-afae-2f80462748bf.png)\r\n",
    "\r\n",
    "\r\n",
    "## Cookie的缺点\r\n",
    "\r\n",
    "在项目中广泛使用 cookie 之前，必须了解它的缺点。\r\n",
    "\r\n",
    "1. `cookie` 并不安全。存储在 cookie 中的数据对任何人都是可见的，所以你不应该用它来存储敏感数据，比如密码、信用卡信息等等。\r\n",
    "\r\n",
    "2. 可以禁用 cookie。大多数浏览器都允许用户选择禁用 cookie。当 cookie 被禁用时，您将不会得到任何警告或错误消息，相反，用于设置 cookie 的响应头文件将被丢弃。为了解决这些问题，您可以像下面这样编写 Javascript 代码，以提醒用户您的应用程序需要 cookie 来正常工作。\r\n",
    "\r\n",
    "```javascript\r\n",
    "<script>\r\n",
    "    document.cookie = \"foo=bar;\";\r\n",
    "    if (!document.cookie) \r\n",
    "    {\r\n",
    "        alert(\"This website requires cookies to function properly\");\r\n",
    "    }\r\n",
    "</script>\r\n",
    "```\r\n",
    "\r\n",
    "3. 每个 Cookie 只能存储不超过4KB 的数据。除此之外，浏览器还对网站可以设置的 cookies 数量施加限制。这个限制因浏览器的不同而不同。一些浏览器接受每个网站50个 cookies，其他浏览器接受30个。\r\n",
    "\r\n",
    "4. 每次您从服务器请求页面时，都会发送 cookie。假设您有20个 cookies，每个 cookies 存储4KB 的数据。这意味着每个请求都有一个80KB 的额外负载！\r\n"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.9.6",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.9.6 64-bit"
  },
  "interpreter": {
   "hash": "c644d696b95f5e0f4df3c6556741cf30bcf9ea6ca93c3e1f29fcf31d885534fc"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}